<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>兔了个兔</title>
  </head>
  <style>
    html,
    body {
      background-color: #efefef;
      height: 100%;
      margin: 0;
      padding: 0;
    }
    #main {
      width: 375px;
      height: 667px;
      margin: auto;
      position: relative;
    }
    #canvas {
      background: linear-gradient(
        to bottom,
        #000000 0%,
        #5788fe 607px,
        white 607px,
        white 100%
      );
    }
    #text_canvas {
      position: absolute;
      left: 0;
      top: 0;
    }
    .gif_img {
      display: none;
    }
    @media (max-width: 375px) {
      #main,
      #mask {
        width: 100%;
        height: 100%;
      }
    }
    .tools {
      position: absolute;
      bottom: 20px;
      left: 0;
      width: 100%;
      text-align: center;
    }
    .btn {
      padding: 5px 12px;
      border-radius: 3px;
      cursor: pointer;
      user-select: none;
      display: inline-block;
      font-size: 14px;
      background-color: #ffeedc;
      color: #ddb196;
    }
    .modal {
      position: absolute;
      top: 30px;
      left: 30px;
      width: calc(100% - 60px);
      height: 166px;
      border-radius: 10px;
      padding: 30px;
      box-sizing: border-box;
      background-color: rgba(87, 136, 254, 0.3);
      display: none;
    }
    .modal .header {
      font-size: 16px;
      line-height: 36px;
      color: white;
    }
    .modal input {
      outline: none;
      border: none;
      line-height: 32px;
      width: 100%;
      border-radius: 5px;
    }
    .modal .footer {
      margin-top: 10px;
      text-align: center;
    }
    .modal .btn {
      background-color: #5788fe;
      color: white;
    }
    .download {
      position: absolute;
      bottom: 20px;
      right: 20px;
      width: 44px;
      height: 44px;
      border-radius: 100%;
      background-color: #eee;
      text-align: center;
      cursor: pointer;
      display: none;
    }
    .download .icon {
      width: 24px;
      height: 24px;
      position: relative;
      top: 8px;
    }
    #result {
      width: 375px;
    }
    #mask {
      width: 375px;
      height: 667px;
      position: absolute;
      left: calc((100% - 375px) / 2);
      top: 0;
      background-color: rgba(0, 0, 0, 0.5);
      display: flex;
      align-items: center;
      justify-content: center;
      display: none;
    }
    .loop {
      width: 120px;
      height: 120px;
      border-radius: 100%;
      position: relative;
    }
    .loop::before {
      content: "";
      display: block;
      width: 100%;
      height: 100%;
      border-radius: 100%;
      border: 5px dashed white;
      position: absolute;
      left: 0;
      top: 0;
      box-sizing: border-box;
      animation: rotate 2s linear infinite;
    }
    #mask .loop .text {
      line-height: 120px;
      text-align: center;
      color: white;
      font-size: 20px;
      font-weight: 900;
    }
    @keyframes rotate {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
  </style>
  <!-- <script src="./require.js"></script> -->
  <script src="./js/gif/gif.js"></script>
  <script src="./js/html2canvas.js"></script>
  <body>
    <div id="main">
      <canvas id="canvas"></canvas>
      <canvas id="text_canvas"></canvas>
      <!-- <div class="tools"><div class="btn" onclick="stop()">暂停</div></div> -->
      <div class="modal" id="modal">
        <div class="header">许下你的新年愿望：</div>
        <input id="wishes" placeholder="2~4个字" type="text" maxlength="4" />
        <div class="footer">
          <div class="btn" onclick="confirm()">马上实现</div>
        </div>
      </div>
      <div class="download" onclick="download()">
        <img src="./img/download.png" alt="" class="icon" />
      </div>
    </div>
    <div id="mask">
      <div class="loop">
        <div class="text">录制中...</div>
      </div>
    </div>
    <img class="gif_img" src="./img/xkbg.png" id="xkbg" alt="" />
    <img class="gif_img" src="./img/running.png" id="running" alt="" />
    <img class="gif_img" src="./img/butterfly.png" id="butterfly" alt="" />
    <img class="gif_img" src="./img/rabbit.png" id="rabbit" alt="" />
    <img class="gif_img" src="./img/radish.png" id="radish" alt="" />
    <script>
      class LuckyFactory {
        // 奔跑的人
        rungifFrameWidth = 800;
        rungifFrameHeight = 1400;
        run_frame = 8;
        cur_run_frame = 1;
        // 蝴蝶
        butterFlyFrameWidth = 300;
        butterFlyFrameHeight = 375;
        fly_frame = 6;
        cur_fly_frame = 1;
        // 背景
        bgFrameWidth = 1500;
        bgFrameHeight = 250;
        bg_frame = 1;
        cur_bg_frame = 1;

        step_id = 1;
        constructor() {
          this.initCanvas();
          this.drawBg();
          this.drawStar();
          this.drawStarMove();
          this.drawRunningMan();
          this.drawButterfly();
          this.drawFlyMove();
          this.drawAnimate();
        }
        initCanvas() {
          const canvas = document.getElementById("canvas");
          const ctx = canvas.getContext("2d");
          const main = document.getElementById("main");
          canvas.width = main.clientWidth;
          canvas.height = main.clientHeight;
          this.canvas = canvas;
          this.ctx = ctx;
          this.main = main;
        }
        getQuestion(id) {
          const target = questions.find((item) => item.id == id);
          if (target) return target;
          console.error(`找不到"id=${id}"相关问题`);
          return false;
        }
        renderHtml() {}
        drawBg() {
          const gifId = "xkbg";
          this[gifId + "_getPoint"] = (next) => {
            const {
              bgFrameWidth,
              bgFrameHeight,
              main,
              bg_frame,
              cur_bg_frame
            } = this;
            const imgWidth = main.clientWidth;
            const imgHeight = (imgWidth * bgFrameHeight) / bgFrameWidth;
            let x = this[gifId + "_x"] || 0;
            if (-x + imgWidth == bgFrameWidth * bg_frame) {
              x = 0;
            }
            const y = main.clientHeight - 60 - bgFrameHeight;
            const point = [
              bgFrameWidth * (cur_bg_frame - 1),
              0,
              bgFrameWidth * bg_frame,
              bgFrameHeight,
              x,
              y,
              bgFrameWidth * bg_frame,
              bgFrameHeight
            ];
            this[gifId + "_x"] = x - 1;
            this[gifId + "_y"] = y;
            return point;
          };
        }
        drawRunningMan() {
          const { rungifFrameWidth, rungifFrameHeight, main, run_frame } = this;
          const gifId = "running";

          const imgWidth = main.clientWidth / 2;
          const imgHeight = (imgWidth * rungifFrameHeight) / rungifFrameWidth;
          const x = 20;
          const y = main.clientHeight - 20 - imgHeight;

          this[gifId + "_interval"] = 100;
          this[gifId + "_x"] = x;
          this[gifId + "_y"] = y;

          this[gifId + "_getPoint"] = (next) => {
            const { cur_run_frame } = this;
            const point = [
              rungifFrameWidth * (cur_run_frame - 1),
              0,
              rungifFrameWidth,
              rungifFrameHeight,
              x,
              y,
              imgWidth,
              imgHeight
            ];

            if (next) {
              this.cur_run_frame =
                cur_run_frame >= run_frame ? 1 : cur_run_frame + 1;
            }
            return point;
          };
        }
        drawButterfly() {
          const { butterFlyFrameWidth, butterFlyFrameHeight, main, fly_frame } =
            this;
          const gifId = "butterfly";
          this[gifId + "_interval"] = 100;
          const imgWidth = 40;
          const imgHeight =
            (imgWidth * butterFlyFrameHeight) / butterFlyFrameWidth;

          const x = main.clientWidth - imgWidth - 20;
          const y = this["running" + "_y"];
          this[gifId + "_x"] = x;
          this[gifId + "_y"] = y;
          this[gifId + "_imgWidth"] = imgWidth;
          this[gifId + "_imgHeight"] = imgHeight;

          this[gifId + "_getPoint"] = (next) => {
            const { cur_fly_frame } = this;
            this.ctx.save();
            this.ctx.translate(x, y);
            this.ctx.rotate((Math.PI / 180) * 15);
            this.ctx.translate(-x, -y);
            const point = [
              butterFlyFrameWidth * (cur_fly_frame - 1),
              0,
              butterFlyFrameWidth,
              butterFlyFrameHeight,
              x,
              this[gifId + "_y"],
              imgWidth,
              imgHeight
            ];

            if (next) {
              this.cur_fly_frame =
                cur_fly_frame >= fly_frame ? 1 : cur_fly_frame + 1;
            }
            return point;
          };
        }
        drawFlyMove(next) {
          const drawId = "drawFlyMove";
          const gifId = "butterfly";
          this[drawId + "_interval"] = 50;
          if (!this[drawId + "_y"]) {
            this[drawId + "_y"] = this[gifId + "_y"];
          }
          const y = this[drawId + "_y"];
          if (next) {
            if (!this[drawId + "_move"] || this[drawId + "_move"] == "down") {
              if (this[gifId + "_y"] + 1 < y + 8) {
                this[drawId + "_move"] = "down";
                this[gifId + "_y"] += 1;
              } else {
                this[drawId + "_move"] = "up";
              }
            } else {
              if (this[gifId + "_y"] - 1 > y - 8) {
                this[gifId + "_y"] -= 1;
              } else {
                this[drawId + "_move"] = "down";
              }
            }
          }
        }
        drawStarOnCanvas({ x, y, r }) {
          this.ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
          this.ctx.strokeStyle = `rgba(255, 255, 255, ${0.5 / 4})`;
          this.ctx.shadowBlur = 12;
          this.ctx.beginPath();
          this.ctx.arc(x, y, r, 0, Math.PI * 2, false);
          this.ctx.closePath();
          this.ctx.fill();
        }
        drawStar(next) {
          const drawId = "drawStar";
          this[drawId + "_interval"] = 1000;
          if (!this[drawId + "_points"]) {
            this[drawId + "_points"] = [];
            for (let i = 1; i < 50; i++) {
              const x =
                20 + Math.floor(Math.random() * (this.main.clientWidth - 40));
              const y =
                80 + Math.floor(Math.random() * (this.main.clientHeight - 160));
              const r = 1 + Math.floor(Math.random() * 2);
              this[drawId + "_points"].push({ x, y, r });
            }
          }
          this[drawId + "_points"] = this[drawId + "_points"].filter(
            (item) => item.y > 0
          );

          if (next) {
            for (let i = 1; i < 3; i++) {
              const x =
                20 + Math.floor(Math.random() * (this.main.clientWidth - 40));
              const y = this.main.clientHeight;
              const r = 1 + Math.floor(Math.random() * 2);
              this[drawId + "_points"].push({ x, y, r });
            }
          }
        }
        drawStarMove(next) {
          const drawId = "drawStarMove";
          const gifId = "drawStar";
          this[drawId + "_interval"] = 100;
          this[gifId + "_points"].forEach((item) => {
            if (next) {
              item.y -= 5;
            }
            this.drawStarOnCanvas(item);
          });
        }
        drawLight() {
          const drawId = "drawLight";
          const gifId = "butterfly";
          const x = this[gifId + "_x"];
          const y = this[gifId + "_y"];
          const imgWidth = this[gifId + "_imgWidth"];
          const imgHeight = this[gifId + "_imgHeight"];
          this.ctx.beginPath();
          const centerX = x + imgWidth / 2 - 8;
          const centerY = y + imgHeight / 2;
          const r = 40;
          const color = this.ctx.createRadialGradient(
            centerX,
            centerY,
            0,
            centerX,
            centerY,
            r
          );
          color.addColorStop(0, "#fff");
          color.addColorStop(0.2, "rgba(251,249,234,1)");
          color.addColorStop(1, "rgba(251,249,234,0)");
          this.ctx.fillStyle = color;
          this.ctx.arc(centerX, centerY, r, 0, Math.PI * 4);
          this.ctx.closePath();
          this.ctx.fill();
        }
        checkAllImgLoaded() {
          const keys = ["xkbg", "running", "butterfly"];
          let flag = true;
          keys.forEach((key) => {
            if (!this[key + "_img"]) {
              flag = false;
            }
          });
          if (flag) {
            this.loaded = true;
          }
        }
        drawGif(gifId, next) {
          const point = this[gifId + "_getPoint"](next);
          if (!this[gifId + "_img"]) {
            const img = document.getElementById(gifId);
            img.onload = () => {
              this[gifId + "_img"] = img;
              this.ctx.drawImage(img, ...point);
              this.checkAllImgLoaded();
            };
          } else {
            this.ctx.drawImage(this[gifId + "_img"], ...point);
          }
          this.ctx.restore();
        }
        drawAnimate(notAnimate) {
          this.ctx.clearRect(
            0,
            0,
            this.main.clientWidth,
            this.main.clientHeight
          );
          const animateIds = [
            "drawStar",
            "drawStarMove",
            "xkbg",
            "running",
            "drawLight",
            "butterfly",
            "drawFlyMove"
          ];
          animateIds.forEach((id) => {
            this[id + "_endTime"] = new Date().getTime();
            if (
              (!this[id + "_startTime"] ||
                (this[id + "_interval"] &&
                  this[id + "_endTime"] - this[id + "_startTime"] >
                    this[id + "_interval"])) &&
              !notAnimate
            ) {
              if (
                [
                  "drawLight",
                  "drawFlyMove",
                  "drawStar",
                  "drawStarMove"
                ].includes(id)
              ) {
                this[id](true);
              } else if (["xkbg", "running", "butterfly"].includes(id)) {
                this.drawGif(id, true);
              }
              this[id + "_startTime"] = this[id + "_endTime"];
            } else {
              if (
                [
                  "drawLight",
                  "drawFlyMove",
                  "drawStar",
                  "drawStarMove"
                ].includes(id)
              ) {
                if (id == "drawStarMove" && notAnimate) {
                  this[id](true);
                } else {
                  this[id]();
                }
              } else if (["xkbg", "running", "butterfly"].includes(id)) {
                this.drawGif(id);
              }
            }
            if (!this[id + "_startTime"]) {
              this[id + "_startTime"] = new Date().getTime();
            }
          });
          if (!notAnimate) {
            this.animationKey = requestAnimationFrame(() => {
              this.drawAnimate.call(this);
            });
          }
        }
        stopRunning(step_num) {
          cancelAnimationFrame(this.animationKey);
          this.ctx.clearRect(
            0,
            0,
            this.main.clientWidth,
            this.main.clientHeight
          );
          this.cur_fly_frame = (step_num % this.fly_frame) + 1;
          this.cur_run_frame = (step_num % this.run_frame) + 1;
          this["xkbg_x"] = this["xkbg_x"] - 5;
          this.drawAnimate(true);
        }
        restartRunning() {
          this.drawAnimate();
        }
      }
      class TextFactory {
        picWidth = 150;
        picHeight = 111;
        radishWidth = 628;
        radishheight = 946;
        constructor({ text }) {
          this.text = text;
          this.init();
        }
        init() {
          const canvas = document.getElementById("text_canvas");
          const ctx = canvas.getContext("2d");
          const main = document.getElementById("main");
          canvas.width = main.clientWidth;
          canvas.height = main.clientHeight / 2;
          this.canvas = canvas;
          this.ctx = ctx;
          this.main = main;
          this.initText();
          this.drawPicture().then(() => {
            this.drawText();
            this.loaded = true;
          });
        }
        initText() {
          const { ctx, text, main, canvas } = this;
          if (!text) return;
          const len = text.length;
          const fontSize = (main.clientWidth - 60 - 15 * (len - 1)) / len;
          canvas.height = fontSize + 80;
          canvas.style.letterSpacing = "7.5px";
          canvas.style.marginTop =
            (main.clientHeight / 2 - canvas.height) / 2 + "px";
          const startX = 30;
          const startY = 40;
          this.fontSize = fontSize;
          this.startX = startX;
          this.startY = startY;
        }
        drawText() {
          const { ctx, text, canvas, fontSize, startX, startY } = this;
          if (!text) return;
          ctx.fillStyle = "white";
          ctx.textBaseline = "top";
          ctx.strokeStyle = "#efefef";
          ctx.font = "normal 800 " + fontSize + "px FZChaoCuHei";
          ctx.fillText(text, startX, startY);
          //   ctx.strokeText(text, startX, startY);
        }
        drawPicture() {
          return Promise.all([this.drawRabbit(), this.drawRadish()]);
        }
        drawRabbit() {
          return new Promise((r, j) => {
            const { fontSize, startX, startY, ctx, picWidth, picHeight } = this;
            let w = Math.floor(fontSize * 0.8);
            let h = Math.floor((w * picHeight) / picWidth);
            //   let x = Math.floor(startX + fontSize * 0.1);
            if (h > 40) {
              h = 40;
              w = Math.floor((h * picWidth) / picHeight);
            }
            let x = Math.floor(
              main.clientWidth - startX - w + (w - fontSize) / 2
            );
            const y = Math.floor(startY - h + 6);
            console.log(x, y, w, h);
            if (!this.img) {
              const img = document.getElementById("rabbit");
              if (img.complete) {
                ctx.drawImage(img, x, y, w, h);
                this.img = img;
                r();
              } else {
                img.onload = () => {
                  this.img = img;
                  ctx.drawImage(img, x, y, w, h);
                  r();
                };
              }
            } else {
              ctx.drawImage(this.img, x, y, w, h);
              r();
            }
          });
        }
        drawRadish() {
          return new Promise((r, j) => {
            const {
              fontSize,
              startX,
              startY,
              ctx,
              radishWidth,
              radishheight,
              main
            } = this;
            let w = Math.floor(fontSize * 1.4);
            let h = Math.floor((w * radishheight) / radishWidth);
            //   let x = Math.floor(
            //     main.clientWidth - startX - w + (w - fontSize) / 2
            //   );
            if (h > fontSize + 60) {
              h = fontSize + 60;
              w = Math.floor((h * radishWidth) / radishheight);
              // x = Math.floor(main.clientWidth - startX - w + (w - fontSize) / 2);
            }
            let x = Math.floor(startX - (w - fontSize) / 2);
            const y = Math.floor(startY - (h - fontSize) / 2 - 20);
            if (!this.radishimg) {
              const img = document.getElementById("radish");
              if (img.complete) {
                ctx.drawImage(img, x, y, w, h);
                this.radishimg = img;
                r();
              } else {
                img.onload = () => {
                  this.radishimg = img;
                  ctx.drawImage(img, x, y, w, h);
                  r();
                };
              }
            } else {
              ctx.drawImage(this.radishimg, x, y, w, h);
              r();
            }
          });
        }
      }
      class MaskController {
        constructor() {
          this.mask = document.getElementById("mask");
        }
        show() {
          this.mask.style.display = "flex";
        }
        hide() {
          this.mask.style.display = "none";
        }
        changeText(text) {
          this.mask.querySelector(".loop .text").innerText = text;
        }
      }
      class ModalController {
        constructor() {
          this.modal = document.getElementById("modal");
        }
        show() {
          this.modal.style.display = "block";
        }
        hide() {
          this.modal.style.display = "none";
        }
      }
      class DownloadController {
        constructor() {
          this.dowload = document.getElementsByClassName("download")[0];
        }
        show() {
          this.dowload.style.display = "block";
        }
        hide() {
          this.dowload.style.display = "none";
        }
      }
      const luckyFactory = new LuckyFactory();
      const maskController = new MaskController();
      const modalController = new ModalController();
      const downloadController = new DownloadController();
      function getWishes() {
        const wishes = localStorage.getItem("my-wishes");
        if (wishes && wishes.length > 1) {
          modalController.hide();
          const text = new TextFactory({ text: wishes.slice(0, 4) });
          const check = () => {
            setTimeout(() => {
              if (luckyFactory.loaded && text.loaded) {
                downloadController.show();
              } else {
                check();
              }
            }, 500);
          };
          check();
        } else {
          modalController.show();
        }
      }
      getWishes();
      function confirm() {
        const wishes = document.getElementById("wishes");
        modalController.hide();
        if (!wishes.value || wishes.value.length < 2) {
          return;
        }
        const text = new TextFactory({ text: wishes.value });
        localStorage.setItem("my-wishes", wishes.value);
        downloadController.show();
      }
      const imgFrams = [];
      function setTimeoutFunc(gif) {
        luckyFactory.stopRunning(imgFrams.length);
        html2canvas(document.querySelector("#main")).then((canvas) => {
          const url = canvas.toDataURL("image/png");
          const img = document.createElement("img");
          img.className = "gif_img download_img";
          img.src = url;
          img.onload = () => {
            // gif.addFrame(img, { copy: true });
            // gif.addFrame(img, { copy: true, delay: 8 * 10 });
            // gif.addFrame(img, { copy: true, delay: 16 * 10 });
            if (imgFrams.length < 8) {
              imgFrams.push(img);
              setTimeoutFunc(gif);
            } else {
              for (let i = 0; i < imgFrams.length * 5; i++) {
                gif.addFrame(imgFrams[i % 8], { copy: true });
              }
              downloadController.show();
              luckyFactory.restartRunning();
              gif.render();
              maskController.changeText("下载中...");
            }
          };
          document.body.appendChild(img);
        });
      }

      function download() {
        maskController.show();
        maskController.changeText("录制中...");
        let gif = new GIF({
          // 创建gif对象
          repeat: 0,
          workers: 2,
          quality: 10,
          workerScript: "./js/gif/gif.worker.js"
        });
        gif.on("finished", function (blob) {
          // 渲染完毕
          maskController.hide();
          var file = new FileReader();
          file.readAsDataURL(blob);
          file.onload = function () {
            const a = document.createElement("a");
            a.download = "新年愿望签.gif";
            a.href = file.result;
            document.body.appendChild(a);
            a.click();
          };
        });
        downloadController.hide();
        const childs = document.getElementsByClassName("download_img");
        if (childs.length) {
          childs.forEach((child) => document.removeChild(child));
        }
        setTimeoutFunc(gif);
      }
    </script>
  </body>
</html>
